package znet

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/cookiejar"
	"strings"
	"time"
)

// 设置 http header 字段的回调。
type SetHttpHeaderCb func(r *http.Request, data interface{}) error

// HTTP GET 请求，可以设置 header 字段。
// 注意：不检查证书，但会检查状态码，不为 200 则认为异常
func HttpGetEx(url string, cb SetHttpHeaderCb, cbdata interface{}) ([]byte, error) {
	if url == "" {
		return nil, fmt.Errorf("httpGetEx param error")
	}

	// 默认为 http 协议
	if !strings.HasPrefix(strings.ToLower(url), "http") {
		url = "http://" + url
	}

	// 跳过证书验证
	tr := &http.Transport{
		TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
		DisableKeepAlives: true,
	}

	// http cookie接口
	cookieJar, _ := cookiejar.New(nil)

	client := &http.Client{
		Jar:       cookieJar,
		Transport: tr,
	}

	reqest, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	// 加头部参数
	reqest.Header.Add("Connection", "close") // 避免长连接导致句柄超限
	if cb != nil {
		if err := cb(reqest, cbdata); err != nil { // 回调加头部
			return nil, err
		}
	}

	resp, err := client.Do(reqest)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// 返回的状态码
	if resp.StatusCode != http.StatusOK {
		/*
			for k, v := range resp.Header {
				fmt.Printf("%s: %s\n", k, strings.Join(v, ","))
			}
		*/
		return nil, fmt.Errorf("ret status code is %v, not equal 200", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

// HTTP POST 请求，可以设置 header 字段。
// 注意：不检查证书，但会检查状态码，不为 200 则认为异常
func HttpPostEx(url, data string, cb SetHttpHeaderCb, cbdata interface{}) ([]byte, error) {
	if url == "" || data == "" {
		return nil, errors.New("httpPostEx param error")
	}

	// 默认为http协议
	if !strings.HasPrefix(strings.ToLower(url), "http") {
		url = "http://" + url
	}

	// 跳过证书验证
	tr := &http.Transport{
		TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
		DisableKeepAlives: true,
	}

	// http cookie接口
	cookieJar, _ := cookiejar.New(nil)

	client := &http.Client{
		Jar:       cookieJar,
		Transport: tr,
	}

	reqest, err := http.NewRequest("POST", url, strings.NewReader(data))
	if err != nil {
		return nil, err
	}

	// 加头部参数
	reqest.Header.Add("Connection", "close") // 避免长连接导致句柄超限
	if cb != nil {
		if err := cb(reqest, cbdata); err != nil { // 回调加头部
			return nil, err
		}
	}

	resp, err := client.Do(reqest)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// 返回的状态码
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("ret status code is %v, not equal 200", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

// HTTP GET 请求。
// 注意：不检查证书，但会检查状态码，不为 200 则认为异常
func HttpGet(url string) (string, error) {
	bs, err := HttpGetEx(url, nil, nil)
	if err != nil {
		return "", err
	}
	return string(bs), nil
}

// HTTP POST 请求，默认 ContentType 为 json。
// 注意：不检查证书，但会检查状态码，不为 200 则认为异常
func HttpPost(url, data, contentType string) (string, error) {
	if contentType == "" {
		contentType = "application/json"
	}

	bs, err := HttpPostEx(url, data, func(r *http.Request, data interface{}) error {
		r.Header.Add("Content-Type", contentType)
		return nil
	}, nil)
	if err != nil {
		return "", err
	}
	return string(bs), nil
}

// 从 conn 接收数据，可以设置超时时间
func RecvTimeout(conn net.Conn, buf []byte, timeout time.Duration) (int, error) {
	if timeout == 0 {
		conn.SetReadDeadline(time.Time{})
	} else {
		conn.SetReadDeadline(time.Now().Add(timeout))
	}

	n, err := conn.Read(buf)
	return n, err
}
